package clasificaciondepizzas.operations.canny;

import java.awt.*;
import java.lang.Math.*;
import java.awt.Color.*;

/**
 * Contains the functionality to generate a gaussian filter kernel and apply
 * it to an image.
 *
 * @author Simon Horne.
 */
public class GaussianSmooth extends Thread {

    /**
     * Default no-args constructor.
     */
    public GaussianSmooth() {
    }

    /**
     * Calculates the discrete value at x,y of the
     * 2D gaussian distribution.
     *
     * @param theta     the theta value for the gaussian distribution
     * @param x         the point at which to calculate the discrete value
     * @param y         the point at which to calculate the discrete value
     * @return          the discrete gaussian value
     */
    public static double gaussianDiscrete2D(double theta, int x, int y) {
        double g = 0;
        for (double ySubPixel = y - 0.5; ySubPixel < y + 0.55; ySubPixel += 0.1) {
            for (double xSubPixel = x - 0.5; xSubPixel < x + 0.55; xSubPixel += 0.1) {
                g = g + ((1 / (2 * Math.PI * theta * theta))
                        * Math.pow(Math.E, -(xSubPixel * xSubPixel + ySubPixel * ySubPixel)
                        / (2 * theta * theta)));
            }
        }
        g = g / 121;
        //System.out.println(g);
        return g;
    }

    /**
     * Calculates several discrete values of the 2D gaussian distribution.
     *
     * @param theta     the theta value for the gaussian distribution
     * @param size      the number of discrete values to calculate (pixels)
     * @return          2Darray (size*size) containing the calculated
     * discrete values
     */
    public static double[][] gaussian2D(double theta, int size) {
        double[][] kernel = new double[size][size];
        for (int j = 0; j < size; ++j) {
            for (int i = 0; i < size; ++i) {
                kernel[i][j] = gaussianDiscrete2D(theta, i - (size / 2), j - (size / 2));
            }
        }

        double sum = 0;
        for (int j = 0; j < size; ++j) {
            for (int i = 0; i < size; ++i) {
                sum = sum + kernel[i][j];

            }
        }

        return kernel;
    }

    /**
     * Takes an image and a gaussian distribution, calculates an
     * appropriate kernel and applies a convolution to smooth the image.
     *
     * @param 2D array representing the input image
     * @param w width of the image
     * @param h height of the image
     * @param ks the required size of the kernel
     * @param theta the gaussian distribution
     * @return 2D array representing the smoothed image
     */
    @SuppressWarnings("static-access")
    public static double[][] smooth(double[][] input, int width, int height,
            int ks, double theta) {
        Convolution convolution = new Convolution();
        double[][] gaussianKernel = new double[ks][ks];
        double[][] output = new double[width][height];
        gaussianKernel = gaussian2D(theta, ks);
        output = convolution.convolution2DPadded(input, width, height,
                gaussianKernel, ks, ks);
        return output;
    }

    /**
     * Takes an input image and a gaussian distribution, calculates
     * an appropriate kernel and applies a convolution to gaussian
     * smooth the image.
     *
     * @param input the input image array
     * @param w the width of the image
     * @param h the height of the image
     * @param ks the size of the kernel to be generated
     * @param theta the gaussian distribution
     * @return smoothed image array
     */
    public static int[] smooth_image(byte[] input, int w, int h,
            int ks, double theta) {
        double[][] input2D = new double[w][h];
        double[] output1D = new double[w * h];
        double[][] output2D = new double[w][h];
        int[] output = new int[w * h];
        //extract greys from input (1D array) and place in input2D

        for (int j = 0; j < h; ++j) {
            for (int i = 0; i < w; ++i) {
                if (input[j * w + i] > 0) {
                    input2D[i][j] = Color.BLACK.getRed();
                } else {
                    input2D[i][j] = Color.WHITE.getRed();
                }


            }
        }

        //now smooth this new 2D array
        output2D = smooth(input2D, w, h, ks, theta);

        for (int j = 0; j < h; ++j) {
            for (int i = 0; i < w; ++i) {
                output1D[j * w + i] = output2D[i][j];
            }
        }
        for (int i = 0; i < output1D.length; ++i) {
            int grey = (int) Math.round(output1D[i]);
            if (grey > 255) {
                grey = 255;
            }
            if (grey < 0) {
                grey = 0;
            }
            //System.out.println(grey);
            output[i] = (new Color(grey, grey, grey)).getRGB();
        }

        return output;
    }
}
